node入门

吴龙淼 写于 2021-09-05

异步非阻塞 I/O, 适合处理高并发,统一 js 编程提高开发效率,轻量化 单线程,cpu 密集运算表现差

npm

^ === n.x 不更新主版本 ~ 只更新补丁版本 “>=1.2.3 <=1.2.3” 指定版本范围 无 不更新任何版本

npm config set registry https://registry.npmmirror.com
npm config set proxy http://username:password@your-proxy-server:port
npm config set https-proxy https://registry.npmmirror.com
npm install(i) -g --save(-S) -dev(-D) -exact(-E) package@5.3 安装依赖
npm ci 先删除node_modules,再严格按照锁文件安装,无锁文件或者冲突报错,速度更快,用于生产
npm uninstall -g  --save -dev package@ 卸载依赖
npm update -g package@  更新依赖
npm list(ls) -g 查看全局本地安装的依赖
npm cache clean --force(f)  清除缓存
npm config ls -l  查看配置
npm config set prefix "url" 包管理配置修改
npm config delete name  包管理配置重置
npm init 生成新的package.json文件
npm version(v) 版本
npm info name 依赖包详细信息
npm root 查看依赖安装路径
npm help 命令帮助
npx 直接使用node_modules库,无需全局安装也能使用命令行

node 各版本新特性

版本 关键特性
12 正式支持 import/export
14 Async Local Storage API, 支持?.
16 支持 Array.prototype.at()等 ES2020 语法, 明确核心模块引用(如 require(‘node:fs’))
18 全局 fetch API, node –watch
20 原生 .env 支持 node –env-file .env-test, import.meta.resolve
22 require() 支持 ESM 模块, node –test, node –run 脚本模式, new WebSocket() API
24 稳定的权限模型:限制文件/子进程访问(–allow-fs-read)

node 模块

process.env 环境变量 process.argv 脚本携带参数,前两项 node 执行路径, 当前执行路径, arg..

http 模块

//模块引入
const http = require('http');
const fs = require('fs');

//定义端口号,地址
const hostname = '127.0.0.1';
const port = 8000;
//创建web服务器 ,回调函数req代表客户端请求 res服务端响应     http.createClient创建客户端
//server.on('request',function(req,res){})
const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  if(req.url !== '/favicon.ico'){
      let out = fs.createWriteStream('./log.txt') // 创建写入流
      out.write(`请求方法:${req.method} \n`)
      out.write(`请求url:${req.url} \n`)
      out.write(`请求头对象:${JSON.stringify(req.headers, null, 4)} \n`)
      out.write(`请求http版本:${req.httpVersion} \n`)
  }
  res.end('Hello World');
});

//启动服务器,监听端口
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

//设置服务器超时
server.timeout=1000

//关闭服务器
server.close([callback])

fs 模块

所有操作分为同步和异步方法

// 最原始繁琐写法
const fs = require('fs')
//同步
let fd = fs.openSync('test.txt','w')
fs.writeSync(fd,'test:hello world')
fs.closeSync(fd)

//异步
fs.open('test.txt','w',function(err, fd){
  if(err){
    console.log(err)
  }else{
    console.log(fd)

    fs.write(fd,'test:hello world',function(err){
      if(!err) console.log('文件已关闭')
      fs.close(fd,function(err){
          if(!err){
            console.log('文件已关闭')
        }
      })
    })
  }
})


流式数据不会一次将数据读入内存,直接文件读写一次性读入内存大文件性能问题,内存泄漏,卡顿等
Stream流对象可绑定事件:
data - 当有数据可读时触发。
end - 没有更多的数据可读时触发。
error - 在接收和写入过程中发生错误时触发。
finish - 所有数据已被写入到底层系统时触发。
管道流用于流之间数据传输:
a.pipe(b).pipe(c)  a数据流写入b数据流再写入c



//简化写入文件
fs.writeFile('test.txt','写入的内容',function(err){
  if(!err)  console.log('写入成功')
})

//流式文件写入
let writeFile = fs.createWriteStream('toPath.txt')  // 创建可写流
writeFile.on[once]('data',function(){
  console.log('流打开了')
})
writeFile.write('写入文件', 'UTF8')
writeFile.close()



//简化读取文件
fs.readFile('test.txt',function(err, data){
  if(!err){
    console.log('写入成功:', data)
    fs.writeFile('test1.txt',data,function(err){
    if(!err)  console.log('写入成功')
})
}
})

//流式文件读取
let readFile = fs.createReadStream('toPath.txt')  // 创建只读流
readFile.on[once]('data',function(data){
  console.log('流打开了')
  console.log(data)
})
//可读流数据存入可写流
readFile.pipe(writeFile)
//写入压缩文件
fs.createReadStream('input.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('input.txt.gz'));
//解压文件
fs.createReadStream('input.txt.gz')
  .pipe(zlib.createGunzip())
  .pipe(fs.createWriteStream('input.txt'));




//其他操作
fs.watch(path, callback)  文件内容变化回调,稳定快
fs.watchFile(path, callback)  文件内容变化回调,跨平台
fs.unlink(path, callback) 删除文件
fs.mkdir(path, callback) 创建目录
fs.rmdir(path, callback) 删除目录
fs.readdir(path, callback) 读取目录
fs.exists() 验证路径是否存在

path 模块

__filename  代表执行文件的绝对路径
__dirname   当前执行文件所在的目录


path.normalize(p) 规范化路径,注意'..' 和 '.'。
path.join([path1][, path2][, ...]) 用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是"\"。
path.resolve([from ...], to) 将 to 参数解析为绝对路径。
path.isAbsolute(path) 判断参数 path 是否是绝对路径。
path.relative(from, to) 用于将相对路径转为绝对路径。
path.dirname(p) 返回路径中代表文件夹的部分,同 Unix 的dirname 命令类似。
path.basename(p[, ext]) 返回路径中的最后一部分。同 Unix 命令 bashname 类似。
path.extname(p) 返回路径中文件的后缀名,即路径中最后一个'.'之后的部分。如果一个路径中并不包含'.'或该路径只包含一个'.' 且这个'.'为路径的第一个字符,则此命令返回空字符串。
path.parse(pathString) 返回路径字符串的对象。
path.format(pathObject) 从对象中返回路径字符串,和 path.parse 相反


var path = require("path");
// 格式化路径
console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));
// 连接路径
console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));
// 转换为绝对路径
path.resolve('home', 'foo', 'build','aaaa','aadada','../../..', 'asset') //return '/home/foo/asset'
// 路径中文件的后缀名(文件类型)
console.log('ext name : ' + path.extname('main.js'));

其他模块

os模块
os.tmpdir() 返回操作系统的默认临时文件夹。
os.endianness() 返回 CPU 的字节序,可能的是 "BE" 或 "LE"。
os.hostname() 返回操作系统的主机名。
os.type() 返回操作系统名
os.platform() 返回操作系统名
os.arch() 返回操作系统 CPU 架构,可能的值有 "x64"、"arm" 和 "ia32"。
os.release() 返回操作系统的发行版本。
os.loadavg() 返回一个包含 1、5、15 分钟平均负载的数组。
os.totalmem() 返回系统内存总量,单位为字节
os.freemem() 返回操作系统空闲内存量,单位是字节。
os.cpus() 返回一个对象数组,包含所安装的每个 CPU/内核的信息:型号、速度(单位 MHz)、时间(一个包含 user、nice、sys、idle 和 irq 所使用 CPU/内核毫秒数的对象)。
os.networkInterfaces() 获得网络接口列表。



events模块
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();
eventEmitter.on('some_event', function() {
    console.log('some_event 事件触发');
});
eventEmitter.emit('some_event', param1, param2, param3)



cluster模块
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('Hello World');
  }).listen(8000);
  console.log(`工作进程 ${process.pid} 已启动`);
}



net模块
提供了一些用于底层的网络通信的小工具,包含了创建服务器/客户端的方法,http模块依赖net模块,且用于创建tcp服务器,socket



dns模块
用于解析域名

buffer(缓存区)

弥补js数组,存储二进制数据,16进制表示,一个buffer占用内存一个byte
存储数据超出8位,取最后8位数据

let buf = new Buffer(1024)
buf.write('test string') 写入buffer,返回写入长度
buf.toString('ascii', 0, 5)
buf.toJSON()
buf.concat([buf1, buf2, buf3])
buf.slice()
buf.length

node 踩坑

模块引入缓存

在同一个文件里面,多次引用同一个模块的话,实际上除了第一次引用之外,其它引用都是缓存

// 清空引入模块缓存
var a = require("./module")
console.log(a.name)
a.name="苏南大叔"
console.log(a.name)
// require.cache中保存的是绝对路径,需要先将相对路径转为绝对路径
delete require.cache[require.resolve("./module")]
var a2 = require("./module2")
console.log(a2.name)

express

npm i express npm i express-session npm i jsonwebtoken express-jwt express 接口编写

//router.js
const express = require('express')
cosnt router = express.Router()
router.get('/get',(req,res)=>{
  const query = req.query
  res.send({
    staus:0,
    msg:'get成功',
    data:query
  })
})
/**
post请求
router.post('/post',(req,res)=>{
  //获得请求体中包含的url-encoded格式数据
  const body = req.body
  res.send({
    staus:0,
    msg:'post成功',
    data:body
  })
})
*/
module.exports = router



//interface.js
const express = require('express')
const app = express()
/**
post请求需要配置中间件
app.use(express.urlencoded({extended:false}))
*/

/**
解决接口跨域问题cors中间件
cors包含http响应头:
access-control-allow-origin  标识允许哪个域的请求
access-control-allow-headers 默认仅支持客户端发送9个限定请求头
access-control-allow-methods 默认支持发get,post,head(简单请求)
const cors = require('cors')
app.use(cors())
*/

/**
//session中间件
const session = require('express-session')
app.use(session({
  secret:'password',
  resave:false,
  saveUninitialized:true
}))
*/

/**
jwt中间件
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
const secretKey = 'myPassword'

//解析客户端token,unless不需要访问权限,自动挂载属性req.user
app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]}))
//生成token,发送给客户端
app.post('api/post',function(req,res){
const token = jwt.sign({username:'aa'},secretKey,{expiresIn:'60s'})
res.send({
  status:200,
  message:'成功',
  token:token
})
})
*/

/**
//错误处理中间件
app.use((err,req,res,next)=>{
if(err.name==='UnauthorizedError'){
  console.log('失败')
  return res.send({
    status:401,
    message:'token无效',
  })
}
  return res.send({
    status:500,
    message:'其他错误',
  })
})
*/

const router = require(./router.js)
app.use('/api',router)
app.listen(80,function(){
  console.log('test')
})

//请求
http:localhost/api/get?test='testName&password=123'
http:localhost/api/post'

pm2

常用命令

pm2 start bootstrap.js -i 1 -n $APP -o /dev/null -e /dev/nul

pm2 start app.js
$ pm2 start -n demo npm -- run dev
$ pm2 start -n demo ./bin/www

# 指定应用程序名称
--name <app_name>

# 当文件改变时,观察并重新启动应用程序
--watch

# 设置应用程序重新加载的内存阈值
--max-memory-restart <200MB>

# 指定日志文件
--log <log_path>

# 向脚本传递额外的参数
-- arg1 arg2 arg3

# 自动重启延迟
--restart-delay <delay in ms>

# 使用时间作为日志前缀
--time

# 不自动重启应用程序
--no-autorestart

# 指定cron强制重启
--cron <cron_pattern>

# 附加到应用程序日志
--no-daemon